/**
 * File: SimpleFileInfoIdx.java
 * Created by: mhaimel
 * Created on: 8 Jul 2009
 * CVS:  $Id: SimpleFileInfoIdx.java,v 1.2 2009/12/07 10:06:46 mhaimel Exp $
 */
package uk.ac.ebi.curtain.service.impl;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.service.GlobalFileInfoIdx;
import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.curtain.utils.concurrent.UtilsCollect;
import uk.ac.ebi.curtain.utils.file.FileInfo;

import com.google.inject.Singleton;

/**
 * @author mhaimel
 *
 */
@Singleton
public class SimpleFileInfoIdx implements GlobalFileInfoIdx {
	private Log log = LogFactory.getLog(this.getClass());
	private Map<FileInfo, Integer> file2Id = UtilsCollect.newConcurrentMap();
//	private List<FileInfo> arrList = new ArrayList<FileInfo>();
	private Map<Integer,FileInfo> fId2File = UtilsCollect.newConcurrentMap();
	private NavigableMap<Long, Integer> position2fileIndex = new ConcurrentSkipListMap<Long, Integer>();
	private Map<Integer, Long> file2positionIndex = UtilsCollect.newConcurrentMap();
	
	public SimpleFileInfoIdx() {
		// do nothing
	}

	/**
	 * {@inheritDoc}
	 */
	public FileInfo getFileInfo(Integer fileId) {
		FileInfo fId = getFileIdMap().get(fileId);
		if(null == fId){
			throw new CurtainUncheckedException("File with Id not registered: " + fileId);
		}
		return fId;
	}

	
	public Integer getId(Long position) {
		return getFileid4Pos(position);
	}

	/**
	 * {@inheritDoc}
	 */
	public FileInfo getFileInfo(Long position) {
		return getFileInfo(getId(position));
	}
	
	private Integer getFileid4Pos(long globalPosition) {
		return getPosition2FileIndex().floorEntry(globalPosition).getValue();
	}	

	private NavigableMap<Long, Integer> getPosition2FileIndex() {
		return position2fileIndex;
	}
	
	private Map<Integer, Long> getFile2PositionIndex() {
		return file2positionIndex ;
	}
	
	public Long translate2LocalPosition(Long globalPosition) {
		Integer fId = getId(globalPosition);
		Long startPosition = getStartPosition(fId);
		return globalPosition-startPosition;
	}

	/**
	 * {@inheritDoc}
	 */
	public Long getStartPosition(FileInfo info) {
		return getStartPosition(getId(info));
	}
	
	private Map<FileInfo, Integer> getFile2Id() {
		return file2Id;
	}

	/**
	 * {@inheritDoc}
	 */
	public Long getStartPosition(Integer fileId) {
		return getFile2PositionIndex().get(fileId);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void register(Integer id, FileInfo info) {
		Integer _intId = getId(info);
		if(null != _intId && !_intId.equals(id)){
			throw new CurtainUncheckedException("File already registered with different ID: " 
					+ _intId 
					+ " instead " 
					+ id 
					+ " for file " + info);
		}
		if(null == _intId){
			_storeEntry(id,info);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public Integer register(FileInfo info) {
		Integer id = getId(info);
		if(null == id){
			id = _getNextId();
			_storeEntry(id,info);
		}
		return id;
	}

	private void _storeEntry(Integer id, FileInfo info) {
		getFileIdMap().put(id, info);
		getFile2Id().put(info, id);		
		// register
		_addGlobalFileEntry(info, id);
		
	}
	
	private Integer _fileId = 0;

	private Integer _getNextId() {
		Integer tmpId = _fileId++;
		while(getFileIdMap().containsKey(tmpId)){
			tmpId = _fileId++;
		}
		return tmpId;
	}

	private Long _addGlobalFileEntry(FileInfo info, Integer id) {
		Long nextKey = 0L;
		if(getPosition2FileIndex().size() > 0){
			Long lastKey = getPosition2FileIndex().lastKey();
			Integer lastFile = getPosition2FileIndex().get(lastKey);
			File file = getFileInfo(lastFile).getFile();
			nextKey = lastKey+file.length();
		}
		getPosition2FileIndex().put(nextKey, id);
		getFile2PositionIndex().put(id, nextKey);
		return nextKey;
	}

	public Integer getId(FileInfo file) {
		return getFile2Id().get(file);
	}

	private Map<Integer, FileInfo> getFileIdMap() {
		return fId2File;
	}

	public void loadIndex(InputStream in) {
		throw new CurtainUncheckedException("Not implemenented yet");
	}

	public void persistIndex(OutputStream out) {
		throw new CurtainUncheckedException("Not implemenented yet");
	}
}
